//
// Copyright 2022 The Sparrow Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#include "sparrow/sysinfo.h"

#include <stdlib.h>
#include <cstring>
#include <chrono>
#include <thread>
#include <sstream>

#include "src/os/platform.h"

namespace sparrow {
#ifdef _WIN32
static std::string WindowsErrorMessage(DWORD error_code) {
    std::string message;
    char *error_text       = NULL;
    size_t error_text_size = ::FormatMessageA(
        FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        reinterpret_cast<char *>(&error_text), 0, NULL);
    if (!error_text) {
        return message;
    }
    message.assign(error_text, error_text_size);
    ::LocalFree(error_text);
    return message;
}
#endif
std::string GetWorkDirectory() {
    uint32_t length = 260;
    std::string result;
    do {
#ifdef _WIN32
        result.resize(length);
        length = GetCurrentDirectoryA(length, const_cast<char *>(result.data()));
        if (length == 0) return std::string();
        if (length < result.size()) {
            result.resize(length);
            break;
        }
#else
        std::string buff(length, '\0');
        if (getcwd(const_cast<char *>(buff.data()), length)) {
            result.assign(buff.c_str());
            break;
        }
        if (errno != ERANGE) {
            break;
        }
#endif
        length *= 2;
    } while (true);
    return result;
}
uint32_t GetPID() {
#ifdef _WIN32
    return ::GetCurrentProcessId();
#elif defined(__linux__)
    return ::syscall(SYS_getpid);
#else
    return getpid();
#endif
}
uint32_t GetTID() {
#ifdef _WIN32
    return ::GetCurrentThreadId();
#elif defined(__linux__)
    return ::syscall(SYS_gettid);
#elif defined(__FreeBSD__)
    long tid = 0;
    ::thr_self(&tid);
    return tid;
#else
    return ::gettid();
#endif
}

uint32_t GetCachedTID() {
    static thread_local uint32_t thread_id = GetTID();
    return thread_id;
}

std::string GetEnv(const std::string &name) {
#ifdef _WIN32
    uint32_t buff_size = 260;
    std::string result;
    do {
        result.resize(buff_size);
        uint32_t len =
            GetEnvironmentVariableA(name.c_str(), const_cast<char *>(result.data()), buff_size);
        if (len == 0) return std::string();
        if (len < buff_size) {
            result.resize(len);
            break;
        }
        buff_size *= 2;
    } while (true);
    return result;
#else
    const char *value = getenv(name.c_str());
    return value ? std::string(value) : std::string();
#endif
}

bool SetEnv(const std::string &name, const std::string &value, bool overwrite) {
#ifdef _WIN32
    (void)overwrite;
    return SetEnvironmentVariableA(name.c_str(), value.c_str());
#else
    return 0 == setenv(name.c_str(), value.c_str(), overwrite ? 1 : 0);
#endif
}

bool DeleteEnv(const std::string &name) {
#ifdef _WIN32
    return SetEnvironmentVariableA(name.c_str(), NULL);
#else
    return 0 == unsetenv(name.c_str());
#endif
}

void SleepForMicroseconds(int micros) {
#ifdef _WIN32
    std::this_thread::sleep_for(std::chrono::microseconds(micros));
#else
    ::usleep(micros);
#endif
}
std::string ErrorDescription(const std::string &prefix) {
#ifdef _WIN32
    DWORD error_code = ::GetLastError();
    std::string desc = WindowsErrorMessage(error_code);
#else
    int error_code = errno;
    std::string desc(std::strerror(error_code));
#endif
    std::stringstream ss;
    if (prefix.empty()) {
        ss << "error";
    } else {
        ss << prefix;
    }
    ss << ": " << error_code << " [" << desc << "]";
    return ss.str();
}
} // namespace sparrow
